import pydevd
import logging
import os
import sys

from absl import app, flags
import pandas as pd

from coin.base.datetime_util import (convert_string_to_dates, iterate_date)
from coin.exchange.base.order_gateway import OrderSide
from coin.experimental.xguo.order_and_feed_stats.logic.order_event_util \
  import (get_order_event_dir, convert_event_type, convert_order_side)
from coin.proto.coin_order_gateway_pb2 import OrderGatewayLog, OrderEvent
from coin.proto.coin_order_enums_pb2 import (BUY_ORDER,
                                             SELL_ORDER,
                                             UNKNOWN_ORDER_SIDE,
                                             BUY_OPEN_ORDER,
                                             BUY_CLOSE_ORDER,
                                             SELL_OPEN_ORDER,
                                             SELL_CLOSE_ORDER)
from coin.experimental.xguo.order_and_feed_stats.app.og_event_config import og_log_config as order_event_config

FLAGS = flags.FLAGS


class OrderEventCSVReader(object):
  def __init__(self, root_path, machine, trading_date, exchange, owner, market_type):
    order_event_dir = get_order_event_dir(root_path,
                                          machine,
                                          trading_date,
                                          exchange,
                                          owner,
                                          market_type)
    self._order_event_dir = order_event_dir

  def read_order_event(self):
    order_event = {}
    if os.path.isdir(self._order_event_dir):
      for file in os.listdir(self._order_event_dir):
        if file.endswith('.csv'):
          symbol = file.split('.')[0]
          file_path = os.path.join(self._order_event_dir, file)
          df = pd.read_csv(file_path, index_col=0)
          df = self._convert_order_event_csv(df)
          df['datetime'] = pd.to_datetime(df['timestamp'], unit='ns')
          df = df.set_index('datetime')
          order_event[symbol] = df
    else:
      print('Order event dir not exist: %s' % self._order_event_dir)
    return order_event

  def _convert_order_event_csv(self, df):
    df['event_type'] = df['event_type'].map(convert_event_type)
    df['side'] = df['side'].map(convert_order_side)
    return df


def _separate_fill(fill_df, qty_threshold):
  fill_in_threshold = pd.DataFrame()
  fill_out_of_threshold = pd.DataFrame()
  within_threshold = True
  total_qty = 0
  for index, fill in fill_df.iterrows():
    if within_threshold:
      if total_qty + fill['fill_qty'] >= qty_threshold:
        qty_in_threshold = qty_threshold - total_qty
        qty_out_of_threshold = total_qty + fill['fill_qty'] - qty_threshold
        df1 = fill.copy()
        df2 = fill.copy()
        df1['fill_qty'] = qty_in_threshold
        df2['fill_qty'] = qty_out_of_threshold
        fill_in_threshold = fill_in_threshold.append(df1)
        fill_out_of_threshold = fill_out_of_threshold.append(df2)
        within_threshold = False
      else:
        fill_in_threshold = fill_in_threshold.append(fill)
        total_qty += fill['fill_qty']
    else:
      fill_out_of_threshold = fill_out_of_threshold.append(fill)
  return fill_in_threshold, fill_out_of_threshold


def _separate_unbalanced_fill(buy_fill_df, sell_fill_df):
  buy_fill_qty = buy_fill_df['fill_qty'].sum()
  sell_fill_qty = sell_fill_df['fill_qty'].sum()
  intraday_realized_qty = min(buy_fill_qty, sell_fill_qty)
  if buy_fill_qty >= sell_fill_qty:
    buy_fill_df, unbalanced_fill = \
      _separate_fill(buy_fill_df, intraday_realized_qty)
    unbalanced_side = OrderSide.BUY
  else:
    sell_fill_df, unbalanced_fill = \
      _separate_fill(sell_fill_df, intraday_realized_qty)
    unbalanced_side = OrderSide.SELL
  return buy_fill_df, sell_fill_df, unbalanced_fill, unbalanced_side


def calculate_fill_stats(fill_df):
  assert len(fill_df) > 0, len(fill_df)
  symbol = fill_df.iloc[0]['symbol']
  open_price = fill_df.iloc[0]['fill_price']
  close_price = fill_df.iloc[-1]['fill_price']
  init_pos = fill_df.iloc[0]['last_pos']
  buy_idx = fill_df['side'].isin([BUY_ORDER, BUY_OPEN_ORDER, BUY_CLOSE_ORDER])
  buy_fill_df = fill_df.loc[buy_idx].reset_index(drop=True)
  sell_idx = fill_df['side'].isin([SELL_ORDER, SELL_OPEN_ORDER, SELL_CLOSE_ORDER])
  sell_fill_df = fill_df.loc[sell_idx].reset_index(drop=True)

  intraday_realized_buy_fill, intraday_realized_sell_fill, \
  unrealized_fill, unrealized_side = \
    _separate_unbalanced_fill(buy_fill_df, sell_fill_df)
  buy_amt = (intraday_realized_buy_fill['fill_price']
             * intraday_realized_buy_fill['fill_qty']).sum()
  sell_amt = (intraday_realized_sell_fill['fill_price']
              * intraday_realized_sell_fill['fill_qty']).sum()
  intraday_realized_pnl = sell_amt - buy_amt

  unrealized_qty = (unrealized_fill['fill_qty'] * unrealized_fill['sign']).sum()
  unrealized_amt = (unrealized_fill['fill_qty'] * unrealized_fill['fill_price']
                    * unrealized_fill['sign']).sum()
  intraday_unrealized_pnl = unrealized_qty * close_price - unrealized_amt
  total_intraday_pnl = intraday_realized_pnl + intraday_unrealized_pnl
  overnight_pos_holding_pnl = (close_price - open_price) * init_pos
  total_pnl = total_intraday_pnl + overnight_pos_holding_pnl

  buy_fill_qty = buy_fill_df['fill_qty'].sum()
  sell_fill_qty = sell_fill_df['fill_qty'].sum()
  fill_qty = buy_fill_qty + sell_fill_qty
  total_volume = (fill_df['fill_price'] * fill_df['fill_qty']).sum()
  """
  print('symbol: %s buy_qty: %.2f sell_qty: %.2f total_qty: %.2f '
        'total_volume: %.2f' % (
        symbol, buy_fill_qty, sell_fill_qty, fill_qty, total_volume))
  print('symbol: %s intraday_realized_pnl: %.2f '
        'intraday_unrealized_pnl: %.2f total_intraday_pnl: %.2f '
        'holding_pnl: %.2f total_pnl: %.2f unrealized_qty: %.2f' % (
        symbol, intraday_realized_pnl, intraday_unrealized_pnl,
        total_intraday_pnl, overnight_pos_holding_pnl, total_pnl,
        unrealized_qty))
  """
  stats = {
      'fill_pq': total_volume,
      'intraday_pnl': total_intraday_pnl,
      'holding_pnl': overnight_pos_holding_pnl,
      'total_pnl': total_pnl
  }
  return stats


class OrderEventStatsCalculator(object):
  def __init__(self, root_path, machine, trading_date, exchange, owner, market_type):
    self._machine = machine
    self._trading_date = trading_date
    self._exchange = exchange
    self._owner = owner
    self._market_type = market_type
    order_event_reader = OrderEventCSVReader(root_path,
                                             machine,
                                             trading_date,
                                             exchange,
                                             owner,
                                             market_type)
    self._order_event = order_event_reader.read_order_event()
    self._stat_oe = {}

  def _calculate_oe_stats(self, order_event_df):
    fill_df = order_event_df.loc[order_event_df['event_type'] ==
                                 OrderEvent.ORDER_FILLED].reset_index(drop=True)
    if len(fill_df) > 0:
      fill_stats = calculate_fill_stats(fill_df)
      return fill_stats

  def get_oe_stats(self):
    total_volume = 0
    intraday_pnl = 0
    holding_pnl = 0
    total_pnl = 0
    for symbol, oedf in self._order_event.items():
      fill_stats = self._calculate_oe_stats(oedf)
      if fill_stats is None:
        continue
      self._stat_oe[symbol] = fill_stats
      total_volume += fill_stats['fill_pq']
      intraday_pnl += fill_stats['intraday_pnl']
      holding_pnl += fill_stats['holding_pnl']
      total_pnl += fill_stats['total_pnl']
    df = pd.DataFrame(self._stat_oe)
    df = df.applymap('{:.8f}'.format)
    print(df.to_string())
    print('total_volume: %.8f\tintraday_pnl: %.8f\tholding_pnl: %.8f\ttotal_pnl: %.8f\t' %
          (total_volume, intraday_pnl, holding_pnl, total_pnl))


def main(_):
  trading_date = FLAGS.trading_date
  root_dir = os.path.expanduser(FLAGS.root_dir)
  assert trading_date, 'trading date must be specified.'
  assert root_dir, 'root dir must be specified.'
  trading_date = convert_string_to_dates(trading_date)[0]
  """
  exchange = FLAGS.exchange
  machine = FLAGS.machine
  market_type = FLAGS.market_type
  owner = FLAGS.owner
  """

  for config in order_event_config:
    exchange = config['exchange']
    market_type = config['market_type']
    machine = config['machine']
    owner = config['owner']
    assert market_type in ['Spot', 'Futures', 'Margin'], market_type
    print('Running for %s %s %s %s %s...' % (exchange, market_type, machine, owner, trading_date))
    sys.stdout.flush()
    # pydevd.settrace('10.81.1.111', port=50000, stdoutToServer=True, stderrToServer=True)
    calculator = OrderEventStatsCalculator(root_dir,
                                           machine,
                                           trading_date,
                                           exchange,
                                           owner,
                                           market_type)
    calculator.get_oe_stats()


if __name__ == '__main__':
  flags.DEFINE_string('trading_date', None, 'Trading date in form of %Y%m%d.')

  flags.DEFINE_string('root_dir', 'order_event', 'root_dir.')

  logging.basicConfig(level='DEBUG', format='%(levelname)8s %(asctime)s %(name)s] %(message)s')
  app.run(main)
